// file:src/kernel/fs/called.c
// autour:jiangxinpeng
// time:2021.2.25
// copyright:(C) by jiangxinepgn,All right are reserved.

#include <os/memcache.h>
#include <os/path.h>
#include <os/driver.h>
#include <os/kernelif.h>
#include <os/spinlock.h>
#include <os/debug.h>
#include <os/fsal.h>
#include <lib/string.h>
#include <sys/ioctl.h>

fsal_path_t *master_path;
fsal_path_t *fsal_path_table;
DEFINE_SPIN_LOCK(fsal_path_table_lock);

int FsalPathTableInit()
{
    fsal_path_table = KMemAlloc(sizeof(fsal_path_t) * FSAL_PATH_MAX);

    if (fsal_path_table != NULL)
    {
        memset(fsal_path_table, 0, sizeof(fsal_path_t) * FSAL_PATH_MAX);
        return 0;
    }
    return -1;
}

// insert fsal,path,alpath to fsal path struct
// if had present return or if no present create new
int FsalPathInsert(fsal_t *fsal, const char *devpath, const char *path, const char *alpath)
{
    fsal_path_t *fpath = FsalPathFind(alpath, 0);
    if (fpath)
        return -1;
    // alloc new fsal path
    fpath = FsalPathAlloc();
    if (!fpath)
        return -1;

    SpinLockDisInterrupt(&fsal_path_table_lock);

    // set fpath info
    fpath->fsal = fsal;

    memset(fpath->path, 0, FSAL_PATH_LEN);
    strcpy(fpath->path, path);
    fpath->path[FSAL_PATH_LEN - 1] = '\0';

    memset(fpath->alpath, 0, FSAL_PATH_LEN);
    strcpy(fpath->alpath, alpath);
    fpath->alpath[FSAL_PATH_LEN - 1] = '\0';

    memset(fpath->devpath, 0, FSAL_PATH_LEN);
    strcpy(fpath->devpath, devpath);
    fpath->devpath[FSAL_PATH_LEN - 1] = '\0';

    // if first insert just is master path
    if (master_path == NULL)
        master_path = fpath;

    SpinUnlockEnInterrupt(&fsal_path_table_lock);

    KPrint("[fsal path] insert fsal %s path %s alpath %s devpath %s\n", fpath->fsal->name, fpath->path, fpath->alpath, fpath->devpath);
    return 0;
}

// when delete path if it is block device try to setdown
static int TrySetdownBlockDevice(const char *devname)
{
    device_handle_t handle = DeviceOpen((char *)devname, 0);
    if (handle < 0)
    {
        KPrint("%s: open device %s failed!\n", __func__, devname);
        return -1;
    }
    DeviceDevCtl(handle, DISKIO_SETDOWN, 0);
    DeviceClose(handle);
    return 0;
}

// remove assign path from fsal path table
int FsalPathRemove(const char *path)
{
    fsal_path_t *fpath;

    SpinLockDisInterrupt(&fsal_path_table_lock);
    for (int i = 0; i < FSAL_PATH_MAX; i++)
    {
        fpath = &fsal_path_table[i];

        if (fpath->fsal != NULL)
        {
            // pyhics path
            if (!strcmp(fpath->path, path))
            {
                memset(fpath, 0, sizeof(fsal_path_t));
                fpath->fsal = NULL;
                SpinUnlockEnInterrupt(&fsal_path_table_lock);
                TrySetdownBlockDevice(fpath->devpath);
                return 0;
            }

            // device path
            char *q = (char *)strrchr(path, '/');
            if (q)
            {
                q++;

                if (!strcmp(q, fpath->devpath))
                {
                    fpath->fsal = NULL;
                    memset(fpath->path, 0, FSAL_PATH_LEN);
                    SpinUnlockEnInterrupt(&fsal_path_table_lock);
                    TrySetdownBlockDevice(fpath->devpath);
                    return 0;
                }
            }
        }
    }
    SpinUnlockEnInterrupt(&fsal_path_table_lock);
    return -1;
}

fsal_path_t *FsalPathAlloc()
{
    fsal_path_t *path;

    for (int i = 0; i < FSAL_PATH_MAX; i++)
    {
        path = &fsal_path_table[i];

        if (path->fsal == NULL)
            return path;
    }
    return NULL;
}

int FsalPathFree(fsal_path_t *path)
{
    if (path != NULL)
    {
        if (path->fsal != NULL)
        {
            path->fsal = NULL;
        }
        return 0;
    }
    return 1;
}

fsal_path_t *FsalPathFindByType(fsal_path_type_t type, const char *path, int inmaster)
{
    if (type < 0 || type >= FSAL_PATH_TYPE_NUM)
        return NULL;

    char *p = (char *)strchr(path, '/');
    char name[MAX_PATH_LEN+1];
    fsal_path_t *fpath;
    char *cmp_name = NULL, *cmp_path = NULL;

    if (!p)
        return NULL;

    memset(name, 0, MAX_PATH_LEN);

    // if path is root assign inmaster filed
    if (*(p + 1) == '\0' && inmaster)
    {
        return master_path;
    }
    p++;
    p = (char *)strchr(p, '/');
    if (p)
    {
        memcpy(name, path, p - path);
    }
    else
    {
        strcpy(name, path);
    }
    cmp_name = name;

    SpinLock(&fsal_path_table_lock);
    for (int i = 0; i < FSAL_PATH_MAX; i++)
    {
        fpath = &fsal_path_table[i];
        if (fpath->fsal)
        {
            // swith fsal path used to cmp
            switch (type)
            {
            case FSAL_PATH_TYPE_PHYSIC:
                cmp_path = fpath->path;
                break;
            case FSAL_PATH_TYPE_VIRTUAL:
                cmp_path = fpath->alpath;
                break;
            case FSAL_PATH_TYPE_DEVICE:
                cmp_path = fpath->devpath;
                break;
            default:
                KPrint(PRINT_ERR "[path] %s:unknow type\n", __func__);
                cmp_name = "unknow path";
                break;
            }
            // cmp path if is likes
            if (!strcmp(cmp_name, cmp_path))
            {
                SpinUnlock(&fsal_path_table_lock);
                return fpath;
            }
        }
    }
    SpinUnlock(&fsal_path_table_lock);

    if (inmaster)
        return master_path;
    return NULL;
}

void BuildPath(const char *path, char *out)
{
    char abspath[MAX_PATH_LEN+1];
    char *p;
    AbsPath(path, abspath);
    p = (char *)strchr(abspath, '/');
    memset(out, 0, MAX_PATH_LEN);
    WashPath(p, out);
}

void AbsPath(const char *path, char *abspath)
{
    char *p;

    // if is abs path
    if (*path != '/')
    {
        // get current work dir
        if (!KFileGetCWD(abspath, MAX_PATH_LEN))
        {
            p = (char *)strchr(abspath, '/');
            if (p != NULL)
            {
                if ((p[0] == '/' && p[1] != '\0'))
                    strcat(abspath, "/");
            }
        }
    }
    // into root dir
    if (path[0] == '/' && path[1] == '\0')
    {
        abspath[0] = '/';
        abspath[1] = '\0';
    }
    else
    {
        // if no abspath,then add to current work path
        // or is abspath
        strcat(abspath, path);
    }
}

void WashPath(char *old, char *new)
{
    if (!old || !new)
        return;

    char *sub_path = old;
    char name[MAX_PATH_LEN+1];
    char *slash_ptr;

    memset(name, 0, MAX_PATH_LEN);

    sub_path = parse_path_after(sub_path, name);
    if (!name[0])
    {
        // return root
        new[0] = '/';
        new[1] = '\0';
        return;
    }
    new[0] = '\0';
    strcat(new, "/");
    while (name[0])
    {
        if (!strcmp("..", name))
        {
            slash_ptr = (char *)strrchr(new, '/'); // get down level path
            if (slash_ptr != new)
                *slash_ptr = 0;
            else // up level is '/'
                *(slash_ptr + 1) = 0;
        }
        else
        {
            if (strcmp(".", name))
            {
                if (strcmp(new, "/"))
                    strcat(new, "/");
                strcat(new, name);
            }
        }
        memset(name, 0, MAX_PATH_LEN);
        // get down lovel path
        if (sub_path)
        {
            sub_path = parse_path_after(sub_path, name);
        }
    }

    int len;
    while ((len = strlen(new)) > 0)
    {
        if (new[len - 1] == '\n' || new[len - 1] == '\r')
        {
            new[len - 1] = '\0';
        }
        else
        {
            break;
        }
    }
}
// parse assign path
// see follow:
// path: "//abc/bc"    result: "a/b"
char *parse_path_after(char *path, char *out)
{
    // if present one or more char "/",we need jump to next char
    while (*path == '/')
        path++;

    while ((*path != '\0') && (*path != '/'))
    {
        *out++ = *path++;
    }

    if (path[0] == '\0')
        return NULL;
    return path;
}

int FsalPathSwitch(fsal_path_t *fpath, char *new, char *cur)
{
    char *start;
    char *top;

    if (fpath != NULL && new != NULL && cur != NULL)
    {
        // get current fsal path
        strcpy(new, fpath->path);
        // search first '/'
        start = (char *)strchr(cur, '/');
        top = start;
        start++;
        // search second '/'
        start = (char *)strchr(start, '/');
        if (start != NULL)
        {
            // path like /a/
            if (*(start + 1) == '\0')
            {
                // invaild second path,we deal with to first path
                *start = '\0';
                // must be fsal path , top path , alpath no is top path.
                if (fpath == master_path && *(top + 1) && strcmp(fpath->alpath, top))
                {
                    strcat(new, top);
                }
            }
            if (!FsalPathFind(top, 0))
            {
                start = top;
            }
            strcat(new, start);
            return 0;
        }
        else
        {
            // no find second path
            if (fpath == master_path && *(top + 1) && strcmp(fpath->alpath, top))
            {
                strcat(new, top);
            }
            else
            {
                strcat(new, "/");
            }
            return 0;
        }
    }
}

void FsalPathPrint()
{
    KPrint("fsal path info:\n");
    fsal_path_t *fpath;
    SpinLockDisInterrupt(&fsal_path_table_lock);
    int i;
    for (i = 0; i < FSAL_PATH_MAX; i++)
    {
        fpath = fsal_path_table + i;
        if (fpath->fsal)
        {
            KPrint("fsal alpath=%s path=%s devpath=%s,fsal=%s\n", fpath->alpath, fpath->path, fpath->devpath, fpath->fsal->name);
        }
    }
    SpinUnlockEnInterrupt(&fsal_path_table_lock);
}